using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using Nop.Core;
using Nop.Core.Domain.Directory;
using Nop.Core.Domain.Orders;
using Nop.Core.Domain.Payments;
using Nop.Services.Common;
using Nop.Services.Configuration;
using Nop.Services.Directory;
using Nop.Services.Orders;
using Nop.Services.Payments;
using System.Collections.Specialized;
using Nop.Core.Data;
using Nop.Services.Customers;
using Nop.Core.Domain.Customers;
using Nop.Services.Logging;

namespace Spotshub.Plugin.Payments.GlobalPayments.Services
{
    public partial class GlobalPaymentsService : IGlobalPaymentsService
    {
        #region Fields

        private readonly IWorkContext _workContext;
        private readonly IOrderTotalCalculationService _orderTotalCalculationService;
        private readonly ICurrencyService _currencyService;
        private readonly IWebHelper _webHelper;
        private readonly HttpRequestBase _request;
        private readonly CurrencySettings _currencySettings;
        private readonly GlobalPaymentsSettings _globalPaymentsSettings;
        private readonly OrderSettings _orderSettings;
        private readonly ICustomerService _customerService;
        private readonly IMobileDeviceHelper _mobileDeviceHelper;
        private readonly IRepository<PaymentCreditCard> _creditCardRepository;
        private readonly IRepository<PaymentProfile> _profileRepository;
        private readonly IRepository<PaymentTransaction> _transactionRepository;

        private readonly ILogger _logger;
        private readonly HttpContextBase _httpContext;

        #endregion

        #region Ctor

        public GlobalPaymentsService(IWebHelper webHelper, IWorkContext workContext,
            IOrderTotalCalculationService orderTotalCalculationService, ICurrencyService currencyService,
            ICustomerService customerService, HttpContextBase httpContext, ILogger logger,
            HttpRequestBase request, IMobileDeviceHelper mobileDeviceHelper,
            IRepository<PaymentCreditCard> creditCardRepository,
            IRepository<PaymentProfile> profileRepository,
            IRepository<PaymentTransaction> transactionRepository,
            CurrencySettings currencySettings, GlobalPaymentsSettings globalPaymentsSettings,
            OrderSettings orderSettings)
        {
            this._webHelper = webHelper;
            this._orderTotalCalculationService = orderTotalCalculationService;
            this._currencyService = currencyService;
            this._customerService = customerService;
            this._globalPaymentsSettings = globalPaymentsSettings;
            this._currencySettings = currencySettings;
            this._orderSettings = orderSettings;
            this._workContext = workContext;
            this._request = request;
            this._httpContext = httpContext;

            this._logger = logger;

            this._mobileDeviceHelper = mobileDeviceHelper;

            this._creditCardRepository = creditCardRepository;
            this._profileRepository = profileRepository;
            this._transactionRepository = transactionRepository;
        }

        #endregion

        #region Utilities

        #region Misc

        private string CreateTransactionId(int creditCardId)
        {
            return creditCardId.ToString();
        }

        private int GetCreditCardIdFromTransaction(string transactionId)
        {
            int ccId = 0;
            int.TryParse(transactionId, out ccId);
            return ccId;
        }

        private bool UseMobile()
        {
            bool useMobileDevice = _mobileDeviceHelper.IsMobileDevice(_httpContext)
                && _mobileDeviceHelper.MobileDevicesSupported()
                && !_mobileDeviceHelper.CustomerDontUseMobileVersion();
            return false;
        }

        #endregion

        #region Direct Services

        protected virtual string GetUID(int customerId, int storeId, string type = "")
        {
            var url = Helper.BuildUrlWithQuery(GetUIDUrl(), GetUIDQueryStrings(customerId, storeId, type));

            if (_globalPaymentsSettings.EnableDebug && !String.IsNullOrEmpty(_globalPaymentsSettings.TestIframeUrl))
            {
                url = _globalPaymentsSettings.TestIframeUrl;
            }

            var uid = Helper.PostDirectService(url, _globalPaymentsSettings.EnableLog);
            if (String.IsNullOrEmpty(uid) && !uid.StartsWith("uID="))
            {
                throw new NopException("Failed to retrieve the uID. URL: {0}", url);
            }

            var strs = uid.Split('=');
            if (strs.Length != 2)
            {
                throw new NopException("the uID result format is incorrect. uID: {0}. ", uid);
            }

            return strs[1];
        }

        protected virtual CardOnFileServiceResult QueryResult(int storeId, string uid)
        {
            var profileId = "";
            var apiToken = "";
            if (_globalPaymentsSettings.EnableSandbox && !String.IsNullOrEmpty(_globalPaymentsSettings.SandboxProfileId) && !String.IsNullOrEmpty(_globalPaymentsSettings.SandboxAPIToken))
            {
                profileId = _globalPaymentsSettings.SandboxProfileId;
                apiToken = _globalPaymentsSettings.SandboxAPIToken;
            }
            else
            {
                var pass = GetMerchPassByStoreId(storeId);
                profileId = pass.ProfileId;
                apiToken = pass.ApiToken;
            }


            Helper.FileLog(
            "InitRequest",
            "CardOnFileServiceResult profileId:" + profileId + "apiToken:" + apiToken,
            _globalPaymentsSettings.EnableLog);

            var url = String.Format("{0}?CRESecureID={1}&CRESecureAPIToken={2}&uID={3}", GetQueryUrl(), profileId, apiToken, uid);

            if (_globalPaymentsSettings.EnableDebug && !String.IsNullOrEmpty(_globalPaymentsSettings.TestIframeUrl))
            {
                url = _globalPaymentsSettings.TestIframeUrl;
            }

            var res = new CardOnFileServiceResult(Helper.GetDirectService(url, _globalPaymentsSettings.EnableLog));
            if (res.Success)
            {
                res.PNRef = res.NameValues["TxnGUID"];
                res.Token = res.NameValues["CRE_Card_Token"];
                res.ApprovalCode = res.NameValues["ApprovalCode"];
                res.Name = res.NameValues["name"];
                if (!String.IsNullOrEmpty(res.Name))
                {
                    res.Name = HttpUtility.UrlDecode(res.Name);
                }
                res.Type = res.NameValues["type"];
                res.Number = res.NameValues["mPAN"];
                var exp = res.NameValues["exp"];
                if (!String.IsNullOrEmpty(exp) && exp.Length == 4)
                {
                    int n;
                    if (int.TryParse(exp.Substring(0, 2), out n))
                    {
                        res.ExpiredMonth = n;
                    }
                    if (int.TryParse(exp.Substring(2, 2), out n))
                    {
                        res.ExpiredYear = n;
                    }
                }
            }
            return res;
        }

        protected virtual RepeatSaleServiceResult DoReaptSale(int creditCardId, decimal amount, string orderId)
        {
            var url = Helper.BuildUrlWithQuery(GetAuthUrl(), GetRepeatSaleQueryStrings(creditCardId, amount, orderId));
            Helper.FileLog(
            "DoReaptSale",
            string.Format("creditCardId:{0}, amount:{1},orderId:{2}", creditCardId, amount, orderId),
            _globalPaymentsSettings.EnableLog);

            if (_globalPaymentsSettings.EnableDebug && !String.IsNullOrEmpty(_globalPaymentsSettings.TestIframeUrl))
            {
                url = _globalPaymentsSettings.TestIframeUrl;
            }

            var res = new RepeatSaleServiceResult(Helper.GetDirectService(url, _globalPaymentsSettings.EnableLog));
            Helper.FileLog(
            "DoReaptSale",
            string.Format("get direct service result:{0}", res.ResponseString),
            _globalPaymentsSettings.EnableLog);
            if (res.Success)
            {
                res.TransactionId = res.NameValues["TxnGUID"];
                res.ApprovalCode = res.NameValues["ApprovalCode"];
            }

            return res;
        }

        #endregion

        #region Repository

        protected virtual PaymentProfile GetMerchPassByStoreId(int storeId)
        {
            // TODO: partner store
            Helper.FileLog(
                   "GetMerchPassByStoreId",
                   "storeId:" + storeId.ToString(),
                   _globalPaymentsSettings.EnableLog);
            var profile = _profileRepository.Table.FirstOrDefault(x => x.StoreId == storeId);
            return profile ?? GetDefaultMerchPass();
        }

        protected virtual PaymentProfile GetDefaultMerchPass()
        {
            return _profileRepository.Table.FirstOrDefault(x => x.StoreId == 1);
        }

        #endregion

        #region URL

        protected virtual NameValueCollection GetCustomerQueryStrings(int customerId)
        {
            var query = new NameValueCollection();
            var customer = _customerService.GetCustomerById(customerId);
            if (customer == null)
            {
                throw new NopException("The current customer doesn't exist.");
            }

            query.Add("currency_code", customer.Currency == null ? "CAD" : customer.Currency.CurrencyCode);
            query.Add("customer_id", customer.Id.ToString());
            query.AddNonEmptyValue("ip_address", customer.LastIpAddress);

            var billingAddr = customer.BillingAddress;
            if (billingAddr != null)
            {
                if (!String.IsNullOrWhiteSpace(billingAddr.FirstName) && !String.IsNullOrWhiteSpace(billingAddr.LastName))
                {
                    query.Add("name", String.Format("{0} {1}", billingAddr.FirstName, billingAddr.LastName));
                }
                query.AddNonEmptyValue("customer_email", billingAddr.Email);
                query.AddNonEmptyValue("customer_zip_code", billingAddr.ZipPostalCode);
            }

            return query;
        }

        protected virtual NameValueCollection GetMerchPassQueryStrings(int storeId)
        {
            var query = new NameValueCollection();


            if (_globalPaymentsSettings.EnableSandbox && !String.IsNullOrEmpty(_globalPaymentsSettings.SandboxProfileId) && !String.IsNullOrEmpty(_globalPaymentsSettings.SandboxAPIToken))
            {
                query.AddNonEmptyValue("CRESecureID", _globalPaymentsSettings.SandboxProfileId);
                query.AddNonEmptyValue("CRESecureAPIToken", _globalPaymentsSettings.SandboxAPIToken);
            }
            else
            {
                var merchPass = GetMerchPassByStoreId(storeId);
                if (merchPass == null)
                {
                    throw new NopException("The merch pass for the store ({0}) doesn't exist.", storeId);
                }
                query.AddNonEmptyValue("CRESecureID", merchPass.ProfileId);
                query.AddNonEmptyValue("CRESecureAPIToken", merchPass.ApiToken);
            }

            return query;
        }

        protected virtual NameValueCollection GetUIDQueryStrings(int customerId, int storeId, string type = "")
        {
            var query = new NameValueCollection();

            query.Add(GetMerchPassQueryStrings(storeId));
            query.Add(GetCustomerQueryStrings(customerId));

            query.Add("action", "buildForm");
            query.Add("sessionId", Guid.NewGuid().ToString());
            query.Add("cre_tokenize", "store_only");
            query.Add("allowed_types", _globalPaymentsSettings.AllowedTypes ?? "Visa|MasterCard");
            //query.Add("formType", UseMobile() ? "5" : "1");
            query.Add("formType", UseMobile() || type == "app" ? "5" : "1");
            query.Add("collectAddress", "0");

            if (type == "logistics")
            {
                if (!String.IsNullOrEmpty(_globalPaymentsSettings.LogisticsCssUrl))
                    query.Add("css_url", _globalPaymentsSettings.LogisticsCssUrl);
                if (!String.IsNullOrEmpty(_globalPaymentsSettings.LogisticsCallbackUrl))
                    query.Add("callback_url", _globalPaymentsSettings.LogisticsCallbackUrl);
            }
            else if (type == "commerce")
            {
                if (!String.IsNullOrEmpty(_globalPaymentsSettings.CommerceCssUrl))
                    query.Add("css_url", _globalPaymentsSettings.CommerceCssUrl);
                if (!String.IsNullOrEmpty(_globalPaymentsSettings.CommerceCallbackUrl))
                    query.Add("callback_url", _globalPaymentsSettings.CommerceCallbackUrl);
            }
            else if (type == "app")
            {
                if (!String.IsNullOrEmpty(_globalPaymentsSettings.AppCssUrl))
                    query.Add("css_url", _globalPaymentsSettings.AppCssUrl);
                if (!String.IsNullOrEmpty(_globalPaymentsSettings.AppCallbackUrl))
                    query.Add("callback_url", _globalPaymentsSettings.AppCallbackUrl);
            }
            else
            {
                query.AddNonEmptyValue("css_url", _globalPaymentsSettings.CssUrl);
                query.AddNonEmptyValue("callback_url", _globalPaymentsSettings.CallbackUrl);
            }
            return query;
        }

        protected virtual NameValueCollection GetRepeatSaleQueryStrings(int creditCardId, decimal amount, string orderId)
        {
            var cc = GetCreditCardById(creditCardId);
            if (cc == null)
            {
                throw new NopException("Credit card ID {0} doesn't exist.");
            }

            var query = new NameValueCollection();

            query.Add(GetMerchPassQueryStrings(cc.PaymentProfile.StoreId));
            query.Add(GetCustomerQueryStrings(cc.CustomerId));
            if (!String.IsNullOrEmpty(orderId))
                query.Add("orderId", orderId);

            query.Add("CRE_Card_Token", cc.Token);
            query.Add("total_amt", amount.ToString("0.00", new CultureInfo("en-US")));
            query.Add("pass_thru", "true");
            return query;
        }

        protected virtual void ValidateUrl(string url)
        {
            // TODO: Add URL verification here
            if (String.IsNullOrEmpty(url))
            {
                throw new NopException("The URL is either not set or empty.");
            }

            if (!Uri.IsWellFormedUriString(url, UriKind.Absolute))
            {
                throw new NopException("The URL is invalid. URL: {0}", url);
            }
        }

        protected virtual string GetSecureFormUrl(string uID)
        {
            Helper.FileLog(
           "InitRequest",
           "GetSecureFormUrl:" + String.Format("{0}?uID={1}", GetFormUrl(), uID),
           _globalPaymentsSettings.EnableLog);

            return String.Format("{0}?uID={1}", GetFormUrl(), uID);
        }

        protected virtual string GetFormUrl(bool debug = false)
        {
            string url = debug ? _globalPaymentsSettings.TestIframeUrl : _globalPaymentsSettings.EnableSandbox ? _globalPaymentsSettings.SandboxFormUrl : _globalPaymentsSettings.FormUrl;
            ValidateUrl(url);
            return url;
        }

        protected virtual string GetUIDUrl()
        {

            string url = _globalPaymentsSettings.DirectServiceUrl + _globalPaymentsSettings.UIDEndpoint;

            if (_globalPaymentsSettings.EnableSandbox)
                url = _globalPaymentsSettings.SandboxDirectServiceUrl + _globalPaymentsSettings.UIDEndpoint;
            ValidateUrl(url);
            Helper.FileLog(
            "InitRequest",
            "GetUIDUrl url:" + url,
            _globalPaymentsSettings.EnableLog);
            return url;
        }

        protected virtual string GetQueryUrl()
        {
            string url = _globalPaymentsSettings.DirectServiceUrl + _globalPaymentsSettings.QueryEndpoint;
            if (_globalPaymentsSettings.EnableSandbox)
                url = _globalPaymentsSettings.SandboxDirectServiceUrl + _globalPaymentsSettings.QueryEndpoint;
            ValidateUrl(url);
            return url;
        }

        protected virtual string GetAuthUrl()
        {
            string url = _globalPaymentsSettings.DirectServiceUrl + _globalPaymentsSettings.AuthorizeEndpoint;
            if (_globalPaymentsSettings.EnableSandbox)
                url = _globalPaymentsSettings.SandboxDirectServiceUrl + _globalPaymentsSettings.AuthorizeEndpoint;
            ValidateUrl(url);
            return url;
        }

        #endregion

        #endregion

        #region Methods

        private List<PaymentCreditCard> MockedCards(int customerId, int storeId)
        {
            var res = new List<PaymentCreditCard>();
            res.Add(new PaymentCreditCard()
            {
                CreditCardNumber = "XXXX1234",
                CreditCardName = "Win Tseng",
                CreditCardExpireMonth = 2,
                CreditCardExpireYear = 2013,
                CreditCardType = "Visa",
                CustomerId = customerId,
                PaymentProfileId = GetMerchPassByStoreId(storeId).Id,
                Id = 1,
                Token = "TOKEN"
            });

            res.Add(new PaymentCreditCard()
            {
                CreditCardNumber = "XXXX5678",
                CreditCardName = "Win Tseng",
                CreditCardExpireMonth = 12,
                CreditCardExpireYear = 2013,
                CreditCardType = "Visa",
                CustomerId = customerId,
                PaymentProfileId = GetMerchPassByStoreId(storeId).Id,
                Id = 2,
                Token = "TOKEN",
                IsDefault = true
            });

            res.Add(new PaymentCreditCard()
            {
                CreditCardNumber = "XXXX9012",
                CreditCardName = "Win Tseng",
                CreditCardExpireMonth = 12,
                CreditCardExpireYear = 2013,
                CreditCardType = "Master Card",
                CustomerId = customerId,
                PaymentProfileId = GetMerchPassByStoreId(storeId).Id,
                Id = 3,
                Token = "TOKEN"
            });

            return res;
        }

        public virtual bool UseOnePageCheckout()
        {
            var useMobileDevice = _mobileDeviceHelper.IsMobileDevice(_httpContext)
                && _mobileDeviceHelper.MobileDevicesSupported()
                && !_mobileDeviceHelper.CustomerDontUseMobileVersion();

            //mobile version doesn't support one-page checkout
            if (useMobileDevice)
                return false;

            //check the appropriate setting
            return _orderSettings.OnePageCheckoutEnabled;
        }

        /// <summary>
        /// Get all stored credit card
        /// </summary>
        /// <param name="customerId"></param>
        /// <returns></returns>
        public virtual List<PaymentCreditCard> GetCreditCardsByCustomer(int customerId, int storeId)
        {
            //return MockedCards(customerId, storeId);
            var paymentProfile = GetMerchPassByStoreId(storeId);
            if (paymentProfile == null || paymentProfile.CreditCards == null || paymentProfile.CreditCards.Count <= 0)
                return new List<PaymentCreditCard>();

            return paymentProfile.CreditCards.Where(x => x.CustomerId == customerId).ToList();
        }
        public virtual String GetPaymentRefundUid(int orderID)
        {
            return _transactionRepository.Table.FirstOrDefault(x => x.OrderId == orderID).UId;
        }
        public virtual String GetPaymentRawResult(int orderId)
        {
            return _transactionRepository.Table.FirstOrDefault(x => x.OrderId == orderId).RawResult;
        }
        public virtual PaymentCreditCard GetCreditCardById(int id)
        {
            //return MockedCards(1, 0).FirstOrDefault(c => c.Id == id);

            return _creditCardRepository.GetById(id);

            //var ccs = GetCreditCardsByCustomer(customerId, storeId);
            //return ccs.SingleOrDefault(c => c.Id == creditCardId);
        }

        //internal PaymentCreditCard GetCreditCard(
        //    int customerId,
        //    int storeId,
        //    string creditCardNumber,
        //    int creditCardExpireMonth,
        //    int creditCardExpireYear,
        //    string creditCardType,
        //    string creditCardName
        //    )
        //{
        //    var ccs = GetCreditCardsByCustomer(customerId, storeId);
        //    return ccs.SingleOrDefault(
        //        c => c.CreditCardNumber == creditCardNumber &&
        //            c.CreditCardExpireMonth == creditCardExpireMonth &&
        //            c.CreditCardExpireYear == creditCardExpireYear &&
        //            c.CreditCardType == creditCardType &&
        //            c.CreditCardName.Equals(creditCardName, StringComparison.InvariantCultureIgnoreCase));
        //}

        /// <summary>
        /// Get the URL to save credit card
        /// </summary>
        /// <param name="customerId"></param>
        /// <param name="storeId"></param>
        /// <returns></returns>
        public virtual CardOnFileUrlServiceResult GetCardOnFileFormUrl(int customerId, int storeId, string type = "")
        {

            Helper.FileLog(
            "InitRequest",
            "GetCardOnFileFormUrl customerId:" + customerId.ToString() + "storeId:" + storeId.ToString() + "type:" + type,
            _globalPaymentsSettings.EnableLog);
            try
            {
                var uID = GetUID(customerId, storeId, type);
                Helper.FileLog(
                "InitRequest",
                "GetCardOnFileFormUrl UID:" + uID,
                _globalPaymentsSettings.EnableLog);
                return new CardOnFileUrlServiceResult()
                {
                    Success = true,
                    URL = GetSecureFormUrl(uID),
                    UID = uID
                };
            }
            catch (Exception exc)
            {
                var res = new CardOnFileUrlServiceResult()
                {
                    Success = false,
                };
                res.Errors.Add(exc.Message);
                return res;
            }
        }

        /// <summary>
        /// Save the credit card token in database
        /// </summary>
        /// <param name="svcResult"></param>
        /// <returns></returns>
        public virtual CardOnFileServiceResult SaveCreditCard(int customerId, int storeId, string uid)
        {
            Helper.FileLog(
           "InitRequest",
           "SaveCreditCard customerId:" + customerId.ToString() + "storeId:" + storeId.ToString() + "uid" + uid,
           _globalPaymentsSettings.EnableLog);
            var res = QueryResult(storeId, uid);
            if (res.Success)
            {
                var oneCreditCard = _creditCardRepository.Table.FirstOrDefault(i => i.CustomerId == customerId && i.CreditCardType == res.Type && i.CreditCardName == res.Name && i.CreditCardNumber == res.Number);

                if (oneCreditCard == null)
                {
                    var cc = new PaymentCreditCard()
                    {
                        CustomerId = customerId,
                        PaymentProfileId = GetMerchPassByStoreId(storeId).Id,
                        Token = res.Token,
                        CreditCardExpireMonth = res.ExpiredMonth,
                        CreditCardExpireYear = res.ExpiredYear,
                        CreditCardNumber = res.Number,
                        CreditCardName = res.Name,
                        CreditCardType = res.Type
                    };
                    _creditCardRepository.Insert(cc);
                    res.CreditCardId = cc.Id;
                }
            }
            return res;
        }

        public virtual RepeatSaleServiceResult RepeatSale(int creditCardId, decimal amount, string orderId)
        {
            return DoReaptSale(creditCardId, amount, orderId);
        }

        /// <summary>
        /// Capture
        /// </summary>
        /// <param name="creditCard"></param>
        /// <param name="amount"></param>
        /// <param name="orderId"></param>
        /// <returns></returns>
        internal ProcessPaymentResult Capture(PaymentCreditCard creditCard, decimal amount, Guid orderGuid)
        {
            var result = new ProcessPaymentResult();

            result.NewPaymentStatus = PaymentStatus.Paid;
            result.CaptureTransactionId = CreateTransactionId(creditCard.Id);

            return result;
        }

        //internal bool VerifyMessageAuthentication(NameValueCollection headers)
        //{
        //    var sb = new StringBuilder();
        //    sb.AppendLine("GlobalPayment server response headers: ");
        //    foreach (var key in headers.AllKeys)
        //    {
        //        sb.AppendFormat("{0} : {1}{2}", key, headers[key], Environment.NewLine);
        //    }

        //    Log(sb.ToString());

        //    return true;
        //}

        //internal void ProcessCallBackRequest(string xmlData)
        //{
        //    if (String.IsNullOrEmpty(xmlData))
        //        return;

        //    try
        //    {
        //    }
        //    catch (Exception exc)
        //    {
        //        Log(string.Format("An error occurred: {0}", exc));
        //    }
        //}

        internal void Log(string msg, Customer customer = null, Exception e = null, bool isErr = false)
        {
            if (isErr || e != null)
            {
                _logger.Error(msg, e, customer);
            }

            if (_globalPaymentsSettings.EnableLog)
            {
                _logger.Information(String.Format("GlobalPay: {0}", msg), null, customer);
            }
        }

        #endregion

    }
}
